﻿using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using Lab620.WcfRadiusService.Clutch;

namespace Lab620.WcfRadiusService.Extentions
{
    public class RadiusValidationBehavior : IEndpointBehavior
    {
        #region Implementation of IEndpointBehavior

        public void Validate(ServiceEndpoint endpoint)
        {
            
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            var inspector = new RadiusMessageInspector();
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        #endregion
    }

    static  class MessageHeaderUtil
    {
        public static bool HasAnyHeaders(this MessageHeaders headers, params RadiusAttributeType[] types)
        {
            foreach (var type in types)
            {
                if (headers.HasHeader(type))
                    return true;
            }
            return false;
        }


        public static bool HasAllHeaders(this MessageHeaders headers, params RadiusAttributeType[] types)
        {
            foreach (var type in types)
            {
                if (!headers.HasHeader(type))
                    return false;
            }
            return true;
        }

        public static bool HasHeader(this MessageHeaders headers, RadiusAttributeType type)
        {
            int header = headers.FindHeader(type.ToString(), RadiusMessageConstants.RadiusEncodingNamespace);
            return header >= 0;
        }
    }

    class RadiusMessageInspector : IDispatchMessageInspector
    {
        #region Implementation of IDispatchMessageInspector

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            ValidateRfcComplianceOnReceive(request);

            var binding = GetCurrentInputBinding();
            var secret = binding.SharedSecret;
            request.Properties["SharedSecret"] = Encoding.ASCII.GetBytes(secret);
            request.Properties["ProfileSource"] = binding.ProfileSource;
            return request.Properties["IncomingPacket"];
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            var incomingPacket = correlationState as RadiusPacket;
            var binding = GetCurrentInputBinding();

            var secret = Encoding.ASCII.GetBytes(binding.SharedSecret);
            reply.Properties["SharedSecret"] = secret;
            reply.Properties["IncomingPacket"] = incomingPacket;
            
        }

        #endregion

        private static void ValidateRfcComplianceOnReceive(Message message)
        {
            var headers = message.Headers;

            if (headers.Action.EndsWith("/Authenticate"))
            {
                // (per RFC2865) check that we have a NASIPAddress and/or NASIdentifier
                if (!headers.HasAnyHeaders(RadiusAttributeType.NASIPAddress, RadiusAttributeType.NASIdentifier))
                {
                    throw new FaultException<RadiusFault>(new RadiusFault("Access-Request did not contain NAS-IP-Address or NAS-Identifier."));
                }

                // (per RFC2865) check that we have a UserPassword or CHAPPassword or State
                if (!headers.HasAnyHeaders(RadiusAttributeType.UserPassword,
                                           RadiusAttributeType.CHAPPassword, RadiusAttributeType.State))
                {
                    throw new FaultException<RadiusFault>(new RadiusFault("Access-Request did not contain User-Password, CHAP-Password, or State."));
                }

                // (per RFC2865) check that there is only a UserPassword or CHAPPassword (not both)
                if (headers.HasAllHeaders(RadiusAttributeType.UserPassword,
                                          RadiusAttributeType.CHAPPassword))
                {
                    throw new FaultException<RadiusFault>(new RadiusFault("Access-Request cannot contain both User-Password and CHAP-Password."));
                }

                // we have at this point:
                // NAS-IP-Address or NAS-Identifier (or both)
                // User-Password or CHAP-Password or State
                // SHOULD contain User-Name
                // SHOULD contain NAS-Port
                // SHOULD contain NAS-Type

                //
                // ---------- PAP AUTHENTICATION ----------
                //

                // this implementation requires the User-Name field
                if (!headers.HasHeader(RadiusAttributeType.UserName))
                {
                    throw new FaultException<RadiusFault>(new RadiusFault("Access-Request must specify User-Name in this implementation."));
                }

                // this implementation requires the User-Password field (PAP)
                if (!headers.HasHeader(RadiusAttributeType.UserPassword))
                {
                    throw new FaultException<RadiusFault>(new RadiusFault("Access-Request must specify User-Password in this implementation."));
                }
            }
        }

        private static RadiusBinding GetCurrentInputBinding()
        {
            var ctx = OperationContext.Current;
            return (RadiusBinding)ctx.Host.Description.Endpoints.Find(ctx.Channel.LocalAddress.Uri).Binding;
        }
    }
}
